﻿// Courtesy Steve Marx
namespace CloudNinja.Metering.BlobLeaseUtil
{
    using System;
    using System.Net;

    using CloudNinja.Metering.Common.Logging;

    using Microsoft.Practices.EnterpriseLibrary.TransientFaultHandling;
    using Microsoft.WindowsAzure.Storage;
    using Microsoft.WindowsAzure.Storage.Blob;
    using Microsoft.WindowsAzure.Storage.Blob.Protocol;

    public class BlobLease : IBlobLease
    {
        private ICloudBlob leaseBlob;

        private readonly RetryPolicy retryPolicy;

        private readonly ILogger logger;

        public BlobLease(CloudBlobClient blobClient, string leaseContainerName, string leaseBlobName, ILogger logger, RetryPolicy retryPolicy)
        {
            this.logger = logger;

            this.retryPolicy = retryPolicy;
            this.retryPolicy.Retrying += (sender, args) =>
                {
                    var message = string.Format(
                        "Transitional retry in BlobLease - Count:{0}, Delay:{1}, Exception:{2}",
                        args.CurrentRetryCount,
                        args.Delay,
                        args.LastException);

                    this.logger.LogInformation(message);
                };


            var container = blobClient.GetContainerReference(leaseContainerName);

            try
            {
                this.retryPolicy.ExecuteAction(() => { container.CreateIfNotExists(); });
            }
            catch (Exception exception)
            {
                this.logger.LogException(exception);
            }

            try
            {
                this.retryPolicy.ExecuteAction(() => { this.leaseBlob = container.GetPageBlobReference(leaseBlobName); });
            }
            catch (Exception exception)
            {
                this.logger.LogException(exception);
            }

            if (this.leaseBlob.Exists())
            {
                return;
            }

            var newBlob = container.GetPageBlobReference(leaseBlobName);
            newBlob.Create(0);
            this.leaseBlob = newBlob;
        }

        public string LeaseId { get; private set; }

        public BlobLeaseStatus LeaseStatus { get; private set; }

        public void AcquireLease()
        {
            if (this.leaseBlob == null)
            {
                throw new ApplicationException("Lease blob is not assigned.");
            }

            try
            {
                if (this.LeaseId == null)
                {
                    this.LeaseId = this.leaseBlob.AcquireLease(TimeSpan.FromSeconds(90), null);
                    this.LeaseStatus = BlobLeaseStatus.Acquired;
                }
                else
                {
                    this.leaseBlob.RenewLease(new AccessCondition { LeaseId = this.LeaseId });
                    this.LeaseStatus = BlobLeaseStatus.Acquired;
                }
            }
            catch (WebException webException)
            {
                var response = webException.Response as HttpWebResponse;
                if (response != null)
                {
                    if (response.StatusCode == HttpStatusCode.Conflict)
                    {
                        this.LeaseId = string.Empty;
                        this.LeaseStatus = BlobLeaseStatus.Failed;
                    }
                }
                else
                {
                    this.logger.LogException(webException);
                }
            }
        }

        public void ReleaseLease()
        {
            try
            {
                this.leaseBlob.ReleaseLease(new AccessCondition { LeaseId = this.LeaseId });
            }
            catch (WebException webException)
            {
                var response = webException.Response as HttpWebResponse;
                if (response != null)
                {
                    if (response.StatusCode != HttpStatusCode.Conflict)
                    {
                        return;
                    }
                }
                else
                {
                    this.logger.LogException(webException);
                    return;
                }

                this.LeaseId = string.Empty;
                this.LeaseStatus = BlobLeaseStatus.Failed;
            }
        }

        public void RenewLease()
        {
            try
            {
                this.leaseBlob.RenewLease(new AccessCondition { LeaseId = this.LeaseId });
            }
            catch (WebException webException)
            {
                var response = webException.Response as HttpWebResponse;
                if (response != null)
                {
                    if (response.StatusCode != HttpStatusCode.Conflict)
                    {
                        return;
                    }
                }
                else
                {
                    this.logger.LogException(webException);
                    return;
                }

                this.LeaseId = string.Empty;
                this.LeaseStatus = BlobLeaseStatus.Failed;
            }
        }

        public void BreakLease()
        {
            try
            {
                this.leaseBlob.BreakLease();
            }
            catch (WebException webException)
            {
                var response = webException.Response as HttpWebResponse;
                if (response != null)
                {
                    if (response.StatusCode != HttpStatusCode.Conflict)
                    {
                        return;
                    }
                }
                else
                {
                    this.logger.LogException(webException);
                    return;
                }

                this.LeaseId = string.Empty;
                this.LeaseStatus = BlobLeaseStatus.Failed;
            }
        }
    }
}